﻿using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;

namespace Elca.MvvmHelpers {

    public class RadioButtonHelper : DependencyObject {

        private static readonly Dictionary<string, List<RadioButton>> s_group2ButtonsMap = new Dictionary<string, List<RadioButton>>();
        private static readonly List<RadioButton> s_knownButtons = new List<RadioButton>(); 

        private static void OnRadioButtonChecked(object sender, RoutedEventArgs e) {
            RadioButton rb = (RadioButton)sender;
            UncheckOtherButtonsInGroup(rb);
        }

        public static bool? GetIsChecked(RadioButton d) {
            return (bool?) d.GetValue(IsCheckedProperty);
        }

        public static void SetIsChecked(RadioButton d, bool? value) {
            d.SetValue(IsCheckedProperty, value);
        }

        public static readonly DependencyProperty IsCheckedProperty =
            DependencyProperty.RegisterAttached("IsChecked", 
                                                typeof(bool?), 
                                                typeof(RadioButtonHelper),
                                                new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.Journal |
                                                                                     FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
                                                                              IsCheckedChanged));

        public static void IsCheckedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {

            var rb = d as RadioButton;
            if (rb == null) {
                throw new Exception("IsChecked attached property only works on a FrameworkElement type");
            }

            RememberRadioButton(rb);

            if ((bool) e.NewValue) {

                rb.IsChecked = true; // this triggers OnRadioButtonChecked => other buttons in the same group will be unchecked
            }
        }

        private static void RememberRadioButton(RadioButton rb) {
            var groupName = GetGroupName(rb);

            // if this button is unknown, add it to the right list, based on its group name
            if (s_knownButtons.Contains(rb)) {
                return;
            }

            s_knownButtons.Add(rb);

            List<RadioButton> existingButtons;
            if (! s_group2ButtonsMap.TryGetValue(groupName, out existingButtons)) {
                // unknown group
                s_group2ButtonsMap[groupName] = new List<RadioButton> {rb};
                RegisterButtonEvents(rb);
            } else {
                if (! existingButtons.Contains(rb)) {
                    existingButtons.Add(rb);
                    RegisterButtonEvents(rb);
                }
            }
        }

        private static void RegisterButtonEvents(RadioButton rb) {
            rb.Unloaded += OnButtonUnloaded;
            rb.Checked += OnRadioButtonChecked;
        }

        private static void OnButtonUnloaded(object sender, RoutedEventArgs e) {
            RadioButton rb = (RadioButton) sender;

            ForgetRadioButton(rb);
        }

        private static void ForgetRadioButton(RadioButton rb) {

            List<RadioButton> existingButtons = s_group2ButtonsMap[GetGroupName(rb)];
            existingButtons.Remove(rb);
            s_knownButtons.Remove(rb);

            UnregisterButtonEvents(rb);
        }

        private static void UnregisterButtonEvents(RadioButton rb) {
            rb.Unloaded -= OnButtonUnloaded;
            rb.Checked -= OnRadioButtonChecked;
        }

        private static void UncheckOtherButtonsInGroup(RadioButton rb) {

            List<RadioButton> existingButtons = s_group2ButtonsMap[GetGroupName(rb)];

            foreach (RadioButton other in existingButtons) {

                if (other != rb) {
                    SetIsChecked(other, false);
                }
            }
            SetIsChecked(rb, true);

        }

        private static string GetGroupName(RadioButton elt) {
            string groupName = elt.GroupName;
            if (String.IsNullOrEmpty(groupName)) {
                groupName = "none"; // any value will do
            }
            return groupName;
        }
    }
}
